iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Modern Web

職缺資訊平台—Jobscanner系列 第 21

[開發] React 從 0 到 0.1 (5)

  • 分享至 

  • xImage
  •  

state 就像是一個快照(snapshot)

Rendering 指的是 React 呼叫元件 (元件是一個 function),return 的 JSX 就是當下畫面的快照,props、事件處理器、區域變數都會在那次 render 時被計算。而這個 UI snapshot 是可以互動的,React 會將 snapshot 和畫面做 match,並和事件處理器做連結

re-render 過程:

  1. React 再一次呼叫 function
  2. function 回傳一個新的 JSX snapshot
  3. React 將這個 snapshot 更新到畫面

state 不像一般的變數,function return 後,state 是不會消失的!state 就像是獨立在 function 以外的空間,事件觸發 setter function,由 React 幫我們更新在獨立空間中的 state。


<button onClick={() => {
  setNumber(number + 1);
  setNumber(number + 1);
  setNumber(number + 1);
}}>+3</button>

Setting state 只會在下一次的 render 做異動,即使連續呼叫三次 setNumber(number + 1) 結果也只會 + 1,因為在函式裡的 number 都是 0,這樣只是在告訴 React 下次 render,請幫我把現在的 number 值 + 1。

在同一次 render 過程中,state 值是固定的,即使在 setNumber(number + 1) 之後讀取 number,number 還是不會變的,因為他們用的是同一份 snapshot。即使是非同步的事件處理函式,React state 值都會維持不變!


React 會等到所有在事件處理函式都執行完成,才去處理 state 的更新。就像服務生不會客人每點一道菜就跑去廚房一次,會一次等客人都點完,確定最終內容才送給廚房。這樣可以確保不會執行太多次 re-render,也代表著直到事件處理函式都執行完成,UI 才會更新,也就是 React 的批次處理。

如果想要在下次 re-render 前,同一個狀態想要更新多次,可傳入函式給 setter function,例如:setNumer(n -=> n +1),用來告訴 React,請幫我利用 state 值執行一些程式碼,而不是單純的替換 state 值,例如:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>+3</button>
    </>
  )
}

這裡的 n => n+1 稱為 updater function,把它傳給 setter 時,React 會把它排進要執行的 queue 裡,當事件處理函式執行完畢,在下一次 render 時,React 會依序執行 queue 的內容,

setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
  • 第一行,React 將 n => n +1 這個 function 加進 queue 中
  • 第二行,React 將 n => n +1 這個 function 加進 queue 中
  • 第三行,React 將 n => n +1 這個 function 加進 queue 中

當下一次 render 時,執行到 useState 時,React 會處理這些 queue,假設先前 number state 為 0,React 遇到第一個 n => n+1 時,會將 number 傳入,並 return 一個值,再接續傳給下一個 updater function,所以 useState 最後回傳 3。

如果在替換掉整個 state 數值之後,再去做 update ? 例如:

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>

setNumber(number + 5)setNumber(n => n + 1) 會被加進 queue 中,再下一次 render 時,React 會依序執行 setNumber(number + 5)setNumber(n => n + 1),最後存入 6。


總結來說,setter function 除了傳值,也可以傳入一個 updater function

  • updater function:函式,必須 return something
  • 值:會將 state 替換成該數值

不論是傳值或是傳函式,都會被放進 queue 中,等下一次元件 render 時,才會執行 queue。


state 可以是任何型式,在 JavaScript 中,數值、字串、boolean,屬於 primitive,state 可以從 0 改成 5,但數字 0 本身它是不會改變的。

而物件中的內容是可以改變的,稱為 mutation,例如:position.x = 5
即使物件屬於 mutation,但在 React 中,物件的操作盡量保持 immutable,最好是整個取代物件,而不是只改物件中的值,對於物件,保持唯讀。

如果要做到深層複製,資料結構又很複雜,React 推薦使用 Immer 這套 library。useImmer(initialState)useState 用法類似,不用使用 ... 展開物件,就跟直接更改物件屬性值的寫法一樣,Immer 會幫我們產生新的物件。

  1. 載入 useImmer
import { useImmer } from "use-immer";
  1. useImmer() 取代 useState()
const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
});
  1. 更新 state (複製出一個物件,把 draft.artwork.city 改掉)
updatePerson(draft => {
  draft.artwork.city = 'Lagos';
});

跟物件一樣,React 中的 state 如果為陣列,應該是直接指派一個新的陣列,而不是直接改裡面的值。

建議使用 concat[...arr]filterslicemap 這些陣列操作的方法。


上一篇
[開發] React 從 0 到 0.1 (4)
下一篇
[開發] React 從 0 到 0.1 (6)
系列文
職缺資訊平台—Jobscanner31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言